#pragma once
#include <iostream>
#include <functional>
#include <memory>
#include <type_traits>
#include <cassert>
#include "Connection.hpp"
#include "./Protocol/Protocol.hpp"

template <typename T>
inline T *get_pointer(const std::shared_ptr<T> &ptr)
{
    return ptr.get();
}

template <typename T>
inline T *get_pointer(const std::unique_ptr<T> &ptr)
{
    return ptr.get();
}

template<typename To, typename From>
inline To implicit_cast(From const &f)
{
  return f;
}

// Adapted from google-protobuf stubs/common.h
// see License in muduo/base/Types.h
template <typename To, typename From>
inline ::std::shared_ptr<To> down_pointer_cast(const ::std::shared_ptr<From> &f)
{
    if (false)
    {
        implicit_cast<From *, To *>(0);
    }

#ifndef NDEBUG
    assert(f == NULL || dynamic_cast<To *>(get_pointer(f)) != NULL);
#endif
    return ::std::static_pointer_cast<To>(f);
}

class Callback
{
public:
    virtual ~Callback() = default;
    virtual void onMessage(const TcpConnectPtr &,
                           const ProtocolPtr &message) const = 0;

private:
};

template <class T>
class CallbackT : public Callback
{
    static_assert(std::is_base_of<Protocol, T>::value,
                  "T must be derived from Protocol.");

public:
    using ProtocolTCallback = std::function <void(const TcpConnectPtr&, const std::shared_ptr<T>&)>;
    CallbackT(const ProtocolTCallback& protocol_callback)
        : _protocol_callback(protocol_callback)
    {
    }


    void onMessage(const TcpConnectPtr& conn, const ProtocolPtr &message) const override
    {
        std::shared_ptr<T> concrete = down_pointer_cast<T>(message);
        assert(concrete != NULL);
        _protocol_callback(conn, concrete);
    }

private:
    ProtocolTCallback _protocol_callback;
};